#pragma once
#include "V2D.h"

/** standard Rect using floats */
#define RectF	Rect<float>


//#define RECT_USES_ALTERNATE_ARCHITECTURE
/**
 * @author mvaganov@hotmail.com
 */
template<typename VTYPE>
struct Rect
{
#ifdef RECT_USES_ALTERNATE_ARCHITECTURE
	/** position (x/y or top-left corner), dimension (width/height) */
	V2D<VTYPE> p, d;
#else
	/** corners of box are stored as opposed to x/y w/h*/
	V2D<VTYPE> m_min, m_max;
#endif

	/** @return top-left */
	inline const V2D<VTYPE> getMin()const
#ifdef RECT_USES_ALTERNATE_ARCHITECTURE
	{return V2D<VTYPE>(p);}
#else
	{return m_min;}
#endif
	/** @return bottom right */
	inline const V2D<VTYPE> getMax()const
#ifdef RECT_USES_ALTERNATE_ARCHITECTURE
	{return p.sum(d);}
#else
	{return m_max;}
#endif

	/** set the left-edge location (resizes the rectangle) */
	inline void setMinX(const VTYPE & a_value)
#ifdef RECT_USES_ALTERNATE_ARCHITECTURE
	{	d.addX(getX()-a_value);p.setX(a_value);	}
#else
	{	m_min.x = a_value;	}
#endif
	/** set the right-edge location (resizes the rectangle) */
	inline void setMaxX(const VTYPE & a_value)
#ifdef RECT_USES_ALTERNATE_ARCHITECTURE
	{	d.setX(a_value-getX());	}
#else
	{	m_max.x = a_value;	}
#endif
	/** set the top-edge location (resizes the rectangle) */
	inline void setMinY(const VTYPE & a_value)
#ifdef RECT_USES_ALTERNATE_ARCHITECTURE
	{	d.addY(getY()-a_value);p.setY(a_value);	}
#else
	{	m_min.y = a_value;	}
#endif
	/** set the bottom-edge location (resizes the rectangle) */
	inline void setMaxY(const VTYPE & a_value)
#ifdef RECT_USES_ALTERNATE_ARCHITECTURE
	{	d.setY(a_value-getY());	}
#else
	{	m_max.y = a_value;	}
#endif

	bool isEqual(const Rect<VTYPE> & b) const
	{
		return getMinX() == b.getMinX()
			&& getMinY() == b.getMinY()
			&& getMaxX() == b.getMaxX()
			&& getMaxY() == b.getMaxY();
	}

	/** puts all 4 corners of the box into the given array */
	inline void getCorners(V2D<VTYPE> * a_array)
	{
		a_array[0].set(getMinX(), getMinY());
		a_array[1].set(getMaxX(), getMinY());
		a_array[2].set(getMinX(), getMaxY());
		a_array[3].set(getMaxX(), getMaxY());
	}

	/** @return y value of top edge */
	inline const VTYPE getMinY()const
	{return getMin().getY();}
	/** @return x value of left edge */
	inline const VTYPE getMinX()const
	{return getMin().getX();}
	/** @return y value of bottom edge */
	inline const VTYPE getMaxY()const
	{return getMax().getY();}
	/** @return x value of right edge */
	inline const VTYPE getMaxX()const
	{return getMax().getX();}

	/** set the upper-left corner location (resizes the rectangle) */
	inline void setMin(const V2D<VTYPE> & p)
	{setMinX(p.x);setMinY(p.y);}
	/** set the lower-right corner location (resizes the rectangle) */
	inline void setMax(const V2D<VTYPE> & p)
	{setMaxX(p.x);setMaxY(p.y);}

	/** set the width value (moves the max value) */
	inline void setWidth(const VTYPE & a_value)
	{	setMaxX(getMinX()+a_value);	}
	/** set the height value (moves the max value) */
	inline void setHeight(const VTYPE & a_value)
	{	setMaxY(getMinY()+a_value);	}

	inline VTYPE getWidth()const
	{return getMaxX()-getMinX();}
	inline VTYPE getHeight()const
	{return getMaxY()-getMinY();}
	/** @return x position of rectangle (left edge location) */
	inline const VTYPE getX() const
	{	return getMinX();	}
	/** @return y position of rectangle (top edge location) */
	inline const VTYPE getY() const
	{	return getMinY();	}
	/** set the x position value (moves the rectangle) */
	inline void setX(const VTYPE & a_value)
	{	
		VTYPE d = getMaxX() - getMinX();
		setMinX(a_value);	
		setMaxX(a_value+d);
	}
	/** set the y position value (moves the rectangle) */
	inline void setY(const VTYPE & a_value)
	{
		VTYPE d = getMaxY() - getMinY();
		setMinY(a_value);
		setMaxY(a_value+d);
	}
	/** the distance vector between the two Rect points */
	inline V2D<VTYPE> diagonal()const
	{return getMax().difference(getMin());}
	
	/** width/height */
	inline V2D<VTYPE> getDimension()const
	{return diagonal();}

	void setDimension(const V2D<VTYPE> & a_dim)
	{setWidth(a_dim.getX());	setHeight(a_dim.getY());}

	inline VTYPE getArea()const	{return getWidth()*getHeight();}

	inline bool equals(const Rect<VTYPE> & b)
	{
		return getMin().equals(b.getMin()) && getMax().equals(b.getMax());
	}

	/** re-constructor */
	inline void set(const V2D<VTYPE> & a_min, const V2D<VTYPE> & a_max)
	{	setMin(a_min);	setMax(a_max);	}

	/** given points in arbitrary order, makes this an inscribed rectangle */
	void setFromPoints(const V2D<VTYPE> & a, const V2D<VTYPE> & b)
	{
		V2D<VTYPE> min(((a.x<b.x)?a.x:b.x),((a.y<b.y)?a.y:b.y));
		V2D<VTYPE> max(((a.x>b.x)?a.x:b.x),((a.y>b.y)?a.y:b.y));
		set(min, max);
	}

	/** default constructor, everything is zero */
	inline Rect(){}
	/** */
	inline Rect(const V2D<VTYPE> & a_min, const V2D<VTYPE> & a_max)
	{setFromPoints(a_min, a_max);}
	/** @param where to center the box, a square a_radius*2 tall and wide */
	inline Rect(const V2D<VTYPE> & a_center, const VTYPE & a_radius)
	{
		V2D<VTYPE> corner(a_radius, a_radius);
		set(a_center.sum(corner), a_center.difference(corner));
	}
	/** re-constructor */
	inline void set(const V2D<VTYPE> & a_center, const VTYPE & a_radius)
	{
		V2D<VTYPE> corner(a_radius, a_radius);
		set(a_center.sum(corner), a_center.difference(corner));
	}
	inline void set(const Rect<VTYPE> & a_box)
	{
		set(a_box.getMin(), a_box.getMax());
	}
	/** @return the radius of the circle that this rect would be inscribed in */
	inline VTYPE getRadius()const
	{return diagonal().quotient(2).length();}
	/** resizes the rectangle by circle-radius (a circle that this rectangle is inscribed in) */
	inline void setRadius(const VTYPE & a_radius)
	{
		V2D<VTYPE> diffRad = diagonal();
		if(diffRad.isZero())
		{
			diffRad.set(1,1);
		}
		VTYPE currentRad = diffRad.quotient(2).length();
		VTYPE ratio = a_radius/currentRad;
		V2D<VTYPE> center(getCenter());
		diffRad.multiply(ratio);
		setMin(center.difference(diffRad));
		setMax(center.sum(diffRad));
	}
	inline V2D<VTYPE> getCenter()const
	{return V2D<VTYPE>::between(getMin(),getMax());}
	inline void setCenter(const V2D<VTYPE> & c)
	{
		V2D<VTYPE> rad = diagonal();
		rad.divide(2);
		setMin(c.difference(rad));
		setMax(c.sum(rad));
	}

	inline Rect expandBorder(const V2D<VTYPE> & a_bevel)
	{
		return Rect<VTYPE>(getMin().difference(a_bevel), 
			getMax().sum(a_bevel));
	}

	inline bool glDraw(bool filled) const
	{
		glBegin(filled?GL_POLYGON:GL_LINE_LOOP);
		V2D<VTYPE> tr(getMaxX(), getMinY());
		V2D<VTYPE> bl(getMinX(), getMaxY());
		getMin().glVertex();
		tr.glVertex();
		getMax().glVertex();
		bl.glVertex();
		glEnd();
		return true;
	}
	inline bool glDraw() const
	{
		return glDraw(false);
	}

	inline bool intersects(const Rect<VTYPE> & b) const
	{
		return!(b.getMaxX() < getMinX() ||	b.getMaxY() < getMinY()
			||	b.getMinX() > getMaxX() ||	b.getMinY() > getMaxY());
	}

	/** @return true if the given Rect is totally contained in this Rect */
	inline bool contains(const Rect<VTYPE> & b) const
	{
		return getMinX() <= b.getMinX() && getMinY() <= b.getMinY()
			&& getMaxX() >= b.getMaxX() && getMaxY() >= b.getMaxY();
	}
	/** @return true if the given point is in this Rect */
	inline bool contains(const V2D<VTYPE> & p) const
	{
		return getMinX() <= p.x && getMinY() <= p.y
			&& getMaxX() >= p.x && getMaxY() >= p.y;
	}

	static const int BAD_VALUE = -1;
	/** used to determine X and Y. should not be used in setField() or getField() */
	static const int X = 0, Y = 1;
	/** MINX/MINY */
	static const int MIN = 0;	//V2D::NUM_DIMENSIONS*0
	/** used by getField() and setField() */
	static const int MINX = MIN+X, MINY = MIN+Y;
	/** MAXX/MAXY */
	static const int MAX = 2;	//V2D::NUM_DIMENSIONS*1
	/** used by getField() and setField() */
	static const int MAXX = MAX+X, MAXY = MAX+Y;
	/** X Position/Y Position */
	static const int POSITION = 4;	//V2D::NUM_DIMENSIONS*2
	/** used by getField() and setField() */
	static const int POSITIONX = POSITION+X, POSITIONY = POSITION+Y;
	/** WIDTH/HEIGHT */
	static const int DIMENSION = 6;	//V2D::NUM_DIMENSIONS*3
	/** used by getField() and setField() */
	static const int WIDTH = DIMENSION+X, HEIGHT = DIMENSION+Y;
	/**
	 * used to grab the opposite side of the range of the given dimension. 
	 * eg: MINX being passed in returns MAXX. MAXY passed in returns MINY.
	 * @return MINX<->MAXX, MINY<->MAXY
	 */

	/** @param a_value {MINX, MINY, MAXX, MAXY} */
	inline const VTYPE getField(int const & a_value) const
	{
		//return ((VTYPE*)this)[a_value];
		switch(a_value)
		{
		case MINX:	return getMinX();
		case MINY:	return getMinY();
		case MAXX:	return getMaxX();
		case MAXY:	return getMaxY();
		case POSITIONX:	return getX();
		case POSITIONY:	return getY();
		case WIDTH:		return getWidth();
		case HEIGHT:	return getHeight();
		}
		return 0;
	}
	/**
	 * a more data-driven way to call setMinX, setMaxX, setMinY, or setMaxY
	 * @param a_dimension {MINX,MAXX,MINY,MAXY}
	 */
	inline void setField(const int & a_dimension, const VTYPE & a_value)
	{
		//((VTYPE*)this)[a_dimension] = a_value;
		switch(a_dimension)
		{
		case MINX:	return setMinX(a_value);
		case MINY:	return setMinY(a_value);
		case MAXX:	return setMaxX(a_value);
		case MAXY:	return setMaxY(a_value);
		case POSITIONX:	return setX(a_value);
		case POSITIONY:	return setY(a_value);
		case WIDTH:		return setWidth(a_value);
		case HEIGHT:	return setHeight(a_value);
		}

	}

	/**
	 * @param a_range {MINX,MAXX,MINY,MAXY}
	 * @return true if this box and the given box share the passed in range 
	 * (x or y), meaing their width/height would overlap
	 */
	inline bool commonRange(const Rect<VTYPE> * a_him, int a_range) const
	{
		switch(a_range)
		{
		case MINX:
		case MAXX:	return (getMaxY() >= a_him->getMinY() && getMinY() <= a_him->getMaxY());
		case MINY:
		case MAXY:	return (getMaxX() >= a_him->getMinX() && getMinX() <= a_him->getMaxX());
		}
		return false;
	}
	/**
	 * @param a_range {MINX,MAXX,MINY,MAXY}
	 * @return true if this box and the given box share the passed in range, excluding equality
	 * (x or y), meaing their width/height would overlap
	 */
	inline bool nonOrthogonalCommonRange(const Rect<VTYPE> * a_him, int a_range) const
	{
		switch(a_range)
		{
		case MINX:
		case MAXX:	return (getMaxY() > a_him->getMinY() && getMinY() < a_him->getMaxY());
		case MINY:
		case MAXY:	return (getMaxX() > a_him->getMinX() && getMinX() < a_him->getMaxX());
		}
		return false;
	}
	/**
	 * @return true if this box and the given box share the passed in range, excluding equality
	 * (x or y), meaing their width/height would overlap
	 */
	inline bool nonOrthogonalIntersect(const Rect<VTYPE> & b) const
	{
		return nonOrthogonalCommonRange(&b, X) && nonOrthogonalCommonRange(&b, Y);
	}

	/**
	 */
	bool isEdgeTouchingEdge(const int & a_myEdge, 
		const Rect<VTYPE> * a_him, const int & a_hisEdge, 
		VTYPE a_touchRange = 1.0/1024) const
	{
		if(a_touchRange == 0.0)
			return (nonOrthogonalCommonRange(a_him, a_myEdge) 
			&& getField(a_myEdge) == a_him->getField(a_hisEdge));
		return (commonRange(a_him, a_myEdge)
			&& (abs(getField(a_myEdge) - a_him->getField(a_hisEdge)) < a_touchRange));
	}

	/**
	 * @return {MINX, MINY, MAXX, MAXY} if this Rectangle touches a_him. 
	 * BAD_VALUE (-1) if they don't touch.
	 */
	int getEdgeTouching(const Rect<VTYPE> * a_him, VTYPE const & a_neighborRange = 1.0/1024)
	{
		int myEdge, hisEdge;
		for(myEdge = 0; myEdge <= MAXY; ++myEdge)
		{
			hisEdge = (myEdge + MAX) % POSITION;
			if(isEdgeTouchingEdge(myEdge, a_him, hisEdge, a_neighborRange))
				return myEdge;
		}
		return BAD_VALUE;
	}

	static const int NUM_DIMENSIONS = V2D<VTYPE>::NUM_DIMENSIONS;
};
